﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Reflection;

namespace Accumulation.Common {
    public class DataMapper {
        public static void Map<T>(T source, T target) where T : class {
            DataMapper.Map<T>(source, target, null);
        }

        public static void Map<T>(T source, T target, Func<T, Object> f) where T : class {
            IList<String> propertyNames = f == null
                ? GetPropertyNames(source.GetType())
                : GetPropertyNames(source.GetType(), f(source));
            foreach (var propertyName in propertyNames) {
                try {
                    object value = MethodCaller.CallPropertyGetter<T>(source, propertyName);
                    if (value != null) {
                        MethodCaller.CallPropertySetter<T>(target, propertyName, value);
                    }
                }
                catch (Exception ex) {
                    throw new ArgumentException("复制对象特性信息发生错误" + ex.Message);
                }
            }
        }

        private static IList<string> GetPropertyNames(Type sourceType, Object ignoreObject) {
            List<string> result = new List<string>();
            List<string> ignoreObjectPropertyList = ignoreObject == null
                ? new List<string>()
                : (List<string>)GetPropertyNames(ignoreObject.GetType());
            PropertyDescriptorCollection props = TypeDescriptor.GetProperties(sourceType);
            foreach (PropertyDescriptor item in props) {
                if (item.IsBrowsable && !ignoreObjectPropertyList.Contains(item.Name)) {
                    result.Add(item.Name);
                }
            }
            return result;
        }

        private static IList<string> GetPropertyNames(Type sourceType) {
            List<string> result = new List<string>();
            PropertyDescriptorCollection props = TypeDescriptor.GetProperties(sourceType);
            foreach (PropertyDescriptor item in props) {
                if (item.IsBrowsable) {
                    result.Add(item.Name);
                }
            }
            return result;
        }
    }
    /// <summary>
    /// 获取或设置
    /// </summary>
    internal class MethodCaller {
        private const BindingFlags propertyFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy;
        private static readonly Dictionary<MethodCacheKey, DynamicMemberHandle> _memberCache =
            new Dictionary<MethodCacheKey, DynamicMemberHandle>();

        public static Object CallPropertyGetter<T>(T source, string propertyName) {
            if (source == null) {
                throw new ArgumentNullException("source", "源对象为空");
            }
            if (string.IsNullOrEmpty(propertyName)) {
                throw new ArgumentException("特性名称为空", "propertyName");
            }
            var mh = GetCachedProperty(source.GetType(), propertyName);
            if (mh.DynamicMemberGet == null) {
                throw new NotSupportedException("指定的对象未提供共用的特性");
            }
            return mh.DynamicMemberGet(source);
        }

        public static void CallPropertySetter<T>(T source, string propertyName, object value) {
            if (source == null) {
                throw new ArgumentNullException("source", "源对象为空");
            }
            if (string.IsNullOrEmpty(propertyName)) {
                throw new ArgumentException("特性名称为空", "propertyName");
            }
            var mh = GetCachedProperty(source.GetType(), propertyName);
            if (mh.DynamicMemberSet == null) {
                throw new NotSupportedException("指定的对象未提供共用的特性");
            }
            mh.DynamicMemberSet(source, value);
        }

        internal static Type[] GetParameterTypes(object[] parameters) {
            List<Type> result = new List<Type>();

            if (parameters == null) {
                result.Add(typeof(object));
            }
            else {
                foreach (object item in parameters) {
                    if (item == null) {
                        result.Add(typeof(object));
                    }
                    else {
                        result.Add(item.GetType());
                    }
                }
            }
            return result.ToArray();
        }

        internal static DynamicMemberHandle GetCachedProperty(Type objectType, string propertyName) {
            MethodCacheKey key = new MethodCacheKey(objectType.FullName, propertyName, GetParameterTypes(null));
            DynamicMemberHandle mh = null;
            if (!_memberCache.TryGetValue(key, out mh)) {
                lock (_memberCache) {
                    if (!_memberCache.TryGetValue(key, out mh)) {
                        PropertyInfo info = objectType.GetProperty(propertyName, propertyFlags);
                        if (info == null)
                            throw new InvalidOperationException(
                              string.Format("未发现指定的 {0} 特性", propertyName));
                        mh = new DynamicMemberHandle(info);
                        _memberCache.Add(key, mh);
                    }
                }
            }
            return mh;
        }
    }
    /// <summary>
    /// 得到唯一的缓存键
    /// </summary>
    internal class MethodCacheKey {
        public string TypeName { get; private set; }
        public string MethodName { get; private set; }
        public Type[] ParamTypes { get; private set; }
        private int _hashKey;

        public MethodCacheKey(string typeName, string methodName, Type[] paramTypes) {
            this.TypeName = typeName;
            this.MethodName = methodName;
            this.ParamTypes = paramTypes;

            _hashKey = typeName.GetHashCode();
            _hashKey = _hashKey ^ methodName.GetHashCode();
            foreach (Type item in paramTypes) {
                _hashKey = _hashKey ^ item.Name.GetHashCode();
            }
        }

        public override bool Equals(object obj) {
            MethodCacheKey key = obj as MethodCacheKey;
            if (key != null &&
                key.TypeName == this.TypeName &&
                key.MethodName == this.MethodName &&
                ArrayEquals(key.ParamTypes, this.ParamTypes))
                return true;

            return false;
        }

        private bool ArrayEquals(Type[] a1, Type[] a2) {
            if (a1.Length != a2.Length)
                return false;

            for (int pos = 0; pos < a1.Length; pos++)
                if (a1[pos] != a2[pos])
                    return false;
            return true;
        }

        public override int GetHashCode() {
            return _hashKey;
        }
    }
    /// <summary>
    /// 得到 Getter 和 Setter 对象
    /// </summary>
    internal class DynamicMemberHandle {
        public string MemberName { get; private set; }
        public Type MemberType { get; private set; }
        public DynamicMemberGetDelegate DynamicMemberGet { get; private set; }
        public DynamicMemberSetDelegate DynamicMemberSet { get; private set; }

        public DynamicMemberHandle(
            string memberName,
            Type memberType,
            DynamicMemberGetDelegate dynamicMemberGet,
            DynamicMemberSetDelegate dynamicMemberSet) {
            MemberName = memberName;
            MemberType = memberType;
            DynamicMemberGet = dynamicMemberGet;
            DynamicMemberSet = dynamicMemberSet;
        }

        public DynamicMemberHandle(PropertyInfo info) :
            this(
                info.Name,
                info.PropertyType,
                DynamicMethodHandlerFactory.CreatePropertyGetter(info),
                DynamicMethodHandlerFactory.CreatePropertySetter(info)) { }
    }
    /// <summary>
    /// Delegate for getting a value.
    /// </summary>
    /// <param name="target">Target object.</param>
    /// <returns></returns>
    public delegate object DynamicMemberGetDelegate(object target);
    /// <summary>
    /// Delegate for setting a value.
    /// </summary>
    /// <param name="target">Target object.</param>
    /// <param name="arg">Argument value.</param>
    public delegate void DynamicMemberSetDelegate(object target, object arg);
    /// <summary>
    /// 动态表达式构造工厂类
    /// </summary>
    internal static class DynamicMethodHandlerFactory {
        public static DynamicMemberGetDelegate CreatePropertyGetter(PropertyInfo property) {
            if (property == null) {
                throw new ArgumentNullException("property", "未提供特性");
            }

            if (!property.CanRead) {
                return null;
            }

            ParameterExpression target = Expression.Parameter(typeof(object), string.Empty);
            Expression body = Expression.Property(Expression.Convert(target, property.DeclaringType), property);

            if (property.PropertyType.IsValueType) {
                body = Expression.Convert(body, typeof(object));
            }

            return Expression.Lambda<DynamicMemberGetDelegate>(body, target).Compile();
        }

        public static DynamicMemberSetDelegate CreatePropertySetter(PropertyInfo property) {
            if (property == null) {
                throw new ArgumentNullException("property", "未提供特性");
            }

            if (!property.CanRead) {
                return null;
            }

            ParameterExpression target = Expression.Parameter(typeof(object), string.Empty);
            ParameterExpression val = Expression.Parameter(typeof(object), string.Empty);
            MemberExpression propertyBody = Expression.Property(Expression.Convert(target, property.DeclaringType), property);
            UnaryExpression propertyValue = Expression.Convert(val, property.PropertyType);
            Expression body = Assign(propertyBody, propertyValue);

            return Expression.Lambda<DynamicMemberSetDelegate>(body, target, val).Compile();
        }

        public static BinaryExpression Assign(Expression left, Expression right) {
            var assign = typeof(Assigner<>).MakeGenericType(left.Type).GetMethod("Assign");
            var assignExpr = Expression.Add(left, right, assign);
            return assignExpr;
        }

        private static class Assigner<T> {
            public static T Assign(ref T left, T right) {
                return (left = right);
            }
        }
    }
}
